using System;
using System.Collections.Generic;
using System.Linq;
using Flaschengeist.Entities;
using Flaschengeist.Processes;
using Flaschengeist.Views;
using UnityEngine;

namespace Flaschengeist 
{
	public class Core : MonoBehaviour 
	{
		private GameView gameView;
		private WaitView waitView;
		private MenuView menuView;
		private SubmitScoreView submitScoreView;
		private ScoreView scoreView;
		private CreditsView creditsView;
		private TrackFailsView trackFailsView;
		private HelpView helpView;
		
		private LoadWorlds loadWorlds;
		private LoadLevel loadLevel;
		private LoadLevelCount loadLevelCount;
		private LoadScore loadScore;
		private SubmitScore submitScore;
		private CalculateNote calculateNote;
		private CalculateLevelScore calculateLevelScore;
		private CalculateTotalScore calculateTotalScore;
		private CalculateRemainingFluid calculateRemainingFluid;

		private int levelCount;
		private int currentLevel;
		private int currentWorld;
		private List<LevelResult> results;
		private IEnumerable<World> worlds;
				
		public void Start()
		{			
			//Create views			
			scoreView = gameObject.GetComponent<ScoreView>();
			submitScoreView = gameObject.GetComponent<SubmitScoreView>();			
			menuView = gameObject.GetComponent<MenuView>();		
			gameView = gameObject.GetComponent<GameView>();			
			waitView = gameObject.GetComponent<WaitView>();		
			creditsView = gameObject.GetComponent<CreditsView>();
			trackFailsView = gameObject.GetComponent<TrackFailsView>();
			helpView = gameObject.GetComponent<HelpView>();
			
			//Create processes					
			loadLevelCount = gameObject.GetComponent<LoadLevelCount>();
			loadWorlds = gameObject.GetComponent<LoadWorlds>();
			loadLevel = gameObject.GetComponent<LoadLevel>();	
			loadScore = gameObject.GetComponent<LoadScore>();	
			submitScore = gameObject.GetComponent<SubmitScore>();
			calculateNote = new CalculateNote();
			calculateLevelScore = new CalculateLevelScore();
			calculateTotalScore = new CalculateTotalScore();	
			calculateRemainingFluid = new CalculateRemainingFluid();
			var compareTracks = new CompareTracks();			
			var validateLevel = new ValidateLevel();
								
			//Bind	
			menuView.ShowScore += LoadScore;
			menuView.ShowCredits += LoadCredits;
			menuView.ShowManual += LoadManual;
			menuView.StartGame += Run;
			loadLevelCount.Completed += StartGame;
			loadWorlds.Completed += FinishLoadWorlds;
			loadLevel.Completed += FinishLoadLevel;
			gameView.FillLevelChanging += calculateRemainingFluid.SetStartFillLevel;		
			gameView.GlasFilled += calculateRemainingFluid.SetEndFillLevel;	
			gameView.GlasFilled += (obj) => calculateRemainingFluid.Process();
			gameView.GlasFilled += calculateNote.Process;
			gameView.GlasReleased += calculateNote.Process;			
			gameView.CompareTracks += compareTracks.Process;
			gameView.Finish += calculateLevelScore.Process;
			compareTracks.Completed += DetectLevelFinish;									
			calculateLevelScore.Completed += validateLevel.Process;
			validateLevel.Completed += FinishLevel;	
			calculateNote.Completed += gameView.UpdateNote;	
			calculateRemainingFluid.Completed += gameView.UpdateRemainingFluid;
			calculateTotalScore.Completed += submitScoreView.UpdateScore; 						
			submitScoreView.Submit += SubmitScore;	
			submitScoreView.Abort += menuView.Show;
			loadScore.Completed += FinishLoadScore;
			submitScore.Completed += LoadScore;
			scoreView.Close += menuView.Show;
			creditsView.Close += menuView.Show;
			helpView.Close += menuView.Show;
			trackFailsView.Close += () => 
			{
				gameView.Continue();
				gameView.Show();
			};
		
			//Fade In Scene	
			gameObject.GetComponent<ScreenFadeIn>().Process();		
			
			//Show menu
			menuView.Show();	
		}
					
		private void Run()
		{
			menuView.Hide();
			waitView.Show();	
			loadLevelCount.Process();
		}

		private void StartGame(int levelCount)
		{			
			Debug.Log(String.Format("Start new game with {0} Levels", levelCount));
			this.currentLevel = Settings.START_LEVEL; 
			this.levelCount = levelCount;
			this.results = new List<LevelResult>();	
			
			loadWorlds.Process();
		}
		
		private void StartLevel(Level level)
		{
			calculateRemainingFluid.SetTotalFluid(level.TotalFluid);
			calculateNote.Inject(level.MaxFillLevel, level.Scale);
			waitView.Hide();
			gameObject.GetComponent<CameraZoomOut>().Process();
			gameView.UpdateLevel(level);
		}
			
		private void LoadLevel()
		{						
			if (currentLevel == levelCount + 1)
			{
				FinishGame();	
			}
			else
			{							
				waitView.Show();			
				loadLevel.Process(currentLevel);
				currentLevel++;
			}			
		}
			
		private void LoadCredits()
		{
			menuView.Hide();
			creditsView.Show();
		}
		
		private void LoadManual()
		{
			menuView.Hide();
			helpView.Show();
		}
		
		private void SubmitScore(ScoreItem score)
		{
			waitView.Show();
			submitScore.Process(score);
		}
		
		private void LoadScore()
		{
			menuView.Hide();
			waitView.Show();
			loadScore.Process();
		}
					
		private void DetectLevelFinish(bool result)
		{
			if (result)
			{
				gameView.FinishLevel();
			}
			else
			{
				gameView.Stop();
				gameView.Hide();
				trackFailsView.Show();
			}
		}
						
		private void FinishGame()
		{
			calculateTotalScore.Process(results);
			submitScoreView.Show();	
		}
		
		private void FinishLevel(LevelResult result)
		{
			var cameraZoomIn = gameObject.AddComponent<CameraZoomIn>();
			var resultView = gameObject.AddComponent<LevelResultView>();
			cameraZoomIn.Completed += () => resultView.Show(result);
			resultView.Close += () => 
			{
				Destroy(cameraZoomIn);
				Destroy(resultView);
				results.Add(result);	
				if (result.Passed)
					LoadLevel();
				else
					FinishGame();
			};
						
			cameraZoomIn.Process();
		}
						
		private void FinishLoadScore(IEnumerable<ScoreItem> score)
		{
			scoreView.UpdateScore(score);
			waitView.Hide();
			scoreView.Show();
		}
		
		private void FinishLoadLevel(Level level)
		{	
			//Audio Files für neue Welt laden
			if (level.WorldId != currentWorld)	
			{
				currentWorld = level.WorldId;
				var loadAudio = gameObject.AddComponent<LoadAudio>();
				loadAudio.Completed += (audios) =>
				{
					Destroy(loadAudio);
					gameView.UpdateAudios(audios);
					StartLevel(level);
				};
				loadAudio.Process(worlds.SingleOrDefault(w => w.Id == currentWorld));
			}
			else
			{
				StartLevel(level);
			}
		}
		
		private void FinishLoadWorlds(IEnumerable<World> worlds)
		{
			this.worlds = worlds;			
			LoadLevel();
		}		
	}
}
